package cn.pku.ss.sirx.sna.ib;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import cn.pku.ss.sirx.sna.ib.bean.EnqueueEvent;
import cn.pku.ss.sirx.sna.ib.bean.MessageBoard;
import cn.pku.ss.sirx.sna.ib.bean.ReplyTree;
import cn.pku.ss.sirx.sna.ib.bean.ReplyTreeNode;

/**
 * A queue for reply trees.
 *
 * @author SirX
 * @version 1.0
 */
public class ReplyTreeQueue implements Iterable<ReplyTree> {
    
    /**
     * Constructor.
     * @param messageBoard a MessageBoard instance. A message board responses to a reply tree queue
     */
    public ReplyTreeQueue(MessageBoard messageBoard) {
        this.messageBoard = messageBoard;
    }
    
    /**
     * Append a reply tree to the end of the queue.
     * @param replyTree the reply tree to append
     * @return whether append successfully
     */
    public boolean enqueue(ReplyTree replyTree) {
        boolean result = false;
        if(this.queue.offer(replyTree)) {
            if(this.rearReplyTree != null) {
                this.rearReplyTree.setNext(replyTree);
                replyTree.setPrevious(this.rearReplyTree);
            }
            this.rearReplyTree = replyTree;
            result = true;
        }
        // call-back:
        this.callEnqueueListeners(new EnqueueEvent(this, replyTree, result));
        return result;
    }
    
    /**
     * Append a lost reply tree node to the end of the queue.
     * A "lost" tree node means a reply by the message board owner to someone else,
     * and the reply tree which contains the lost tree node has been destroyed.
     * @param replyTreeNode a lost reply tree node
     * @return whether append successfully
     */
    public boolean enqueueLostReply(ReplyTreeNode replyTreeNode) {
        /*
         * Because a newly enqueued lost tree node has no relation to the previous,
         * we don't need to maintain such a queue structure.
         */
        // call-back:
        this.callEnqueueListeners(new EnqueueEvent(this, replyTreeNode, true));
        return true;
    }
    
    /**
     * Remove a reply tree from the beginning of the queue.
     * @return the removed reply tree
     */
    public ReplyTree dequeue() {
        ReplyTree result = this.queue.poll();
        if (result!=null) {
            if (queue.isEmpty()) {
                this.rearReplyTree = null;
            }
            else {
                this.queue.peek().setPrevious(null);
            }
        }
        return result;
    }
    
    /**
     * Get the head reply tree of the queue.
     * @return the head reply tree of the queue, or null if the queue is empty
     */
    public ReplyTree peek() {
        return this.queue.peek();
    }
    
    /**
     * Get the rear reply tree of the queue.
     * @return the rear reply tree of the queue, or null if the queue is empty
     */
    public ReplyTree rear() {
        return this.rearReplyTree;
    }
    
    public boolean isEmpty() {
        return this.queue.isEmpty();
    }
    
    public void clear() {
        this.queue.clear();
    }

    @Override
    public Iterator<ReplyTree> iterator() {
        return this.queue.iterator();
    }
    
    /**
     * Add a listener called when an enqueue event is activated.
     * @param enqueueListener a listener listening to the enqueue event
     * @return whether the add operation succeeded
     */
    public boolean addEnqueueListener(IEnqueueListener enqueueListener) {
        return this.enqueueListeners.add(enqueueListener);
    }
    
    /**
     * Remove a listener which listens to the enqueue event.
     * @param enqueueListener a listener listening to the enqueue event
     * @return whether the remove operation succeeded
     */
    public boolean removeEnqueueListener(IEnqueueListener enqueueListener) {
        return this.enqueueListeners.remove(enqueueListener);
    }
    
    /**
     * Notify all listeners in the queue that the queue's work has been finished.
     */
    public void endEnqueue() {
        for (IEnqueueListener enqueueListener : this.enqueueListeners) {
            enqueueListener.onEnqueue(EnqueueEvent.EMPTY);
        }
    }
    
    public MessageBoard getMessageBoard() {
        return messageBoard;
    }
    
    //////////////////////////////////////////////////////////////////////////////

    private void callEnqueueListeners(EnqueueEvent enqueueEvent) {
        for (IEnqueueListener enqueueListener : this.enqueueListeners) {
            try {
                enqueueListener.onEnqueue(enqueueEvent);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    //////////////////////////////////////////////////////////////////////////////

    private MessageBoard messageBoard;
    private Queue<ReplyTree> queue = new LinkedList<ReplyTree>(); 
    private ReplyTree rearReplyTree; // reference to the queue rear
    private List<IEnqueueListener> enqueueListeners = new ArrayList<IEnqueueListener>();

}
